//粒子文字效果
import { useState, useEffect, useRef, useMemo, useCallback } from 'react'
import { CanvasStyle, FrameStyle } from './style';
import WithErrorBoundary from '@/WithErrorBoundary';

interface DotItem {
  x: number,
  y: number,
  toX: number,
  toY: number,
  speedX: number,
  speedY: number,
  color: string,
  isArrive: boolean,
}
function ParticleText() {
  /** 随机文字*/
  const sentenceList = ['Hello World', 'Canvas', '掘金你好', '前端']
  const frameDom = useRef<any>(null);
  /** 粒子总数*/
  const dotTotal = useRef(1200)
  const canvasDom = useRef<any>(null);
  const canvasCtx = useRef<any>(null);
  const [height, setHeight] = useState(0)
  const [width, setWidth] = useState(0)
  /** 粒子信息列表*/
  const allDot = useRef<DotItem[]>([])
  /** 文字粒子信息*/
  const textCoordinateList = useRef<{
    x: number,
    y: number
  }[]>([])
  const moveAnimation = useRef<any>(null)
  /** 文字粒子和粒子全部信息的映射 */
  let map = useRef(new Map());

  /** 生成随机数*/
  const createRandomNum = useCallback((min: number, max: number) => {
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }, [])

  /** 生成随机颜色*/
  function getRandomColor() {
    var letters = '0123456789ABCDEF';
    var color = '#';
    for (var i = 0; i < 6; i++) {
      color += letters[Math.floor(Math.random() * 16)];
    }
    return color;
  }

  /** 绘制点*/
  const pointPlot = useCallback((x: number, y: number, color: string) => {
    canvasCtx.current.beginPath()
    canvasCtx.current.strokeStyle = color;
    canvasCtx.current.arc(x, y, 1, 0, 2 * Math.PI);
    canvasCtx.current.stroke();
  }, [])

  /** 是否全部到达 */
  const isAllArrive = useCallback(() => {
    let isTrue = true
    for (let i = 0; i < allDot.current.length; i++) {
      if (!allDot.current[i].isArrive) {
        isTrue = false
      }
    }
    return isTrue
  }, [])

  /** 绘制移动动画*/
  const drawMove = useCallback(() => {
    canvasCtx.current.clearRect(0, 0, width, height)
    for (let i = 0; i < allDot.current.length; i++) {
      let { x: currentX, y: currentY, toX, toY, speedX, speedY } = allDot.current[i]
      let x = 0;
      let y = 0;
      x = currentX + speedX
      y = currentY + speedY

      //边界判断
      if (speedX < 0 && x < toX ||
        speedX > 0 && x > toX
      ) {
        x = toX
        allDot.current[i] = {
          ...allDot.current[i],
          isArrive: true,
        }
      }
      if (speedY < 0 && y < toY ||
        speedY > 0 && y > toY
      ) {
        y = toY;
        allDot.current[i] = {
          ...allDot.current[i],
          isArrive: true,
        }
      }

      pointPlot(x, y, allDot.current[i].color)
      allDot.current[i] = {
        ...allDot.current[i],
        x, y,
      }
    }
    moveAnimation.current = requestAnimationFrame(drawMove)

    //全部粒子到达目标位置，停止动画
    if (isAllArrive()) {
      cancelAnimationFrame(moveAnimation.current)
    }
  }, [width, height])


  /** 设置文字坐标信息*/
  const setLiteralCoordinate = useCallback(() => {
    let index = createRandomNum(0, sentenceList.length - 1);
    let text = sentenceList[index]
    canvasCtx.current.font = "120px Arial"
    canvasCtx.current.fillStyle = "red"
    let textWidth = canvasCtx.current.measureText(text).width;
    canvasCtx.current.fillText(text, width / 2 - textWidth / 2, height / 2)

    let startX = width / 2 - textWidth / 2
    let endX = startX + textWidth;
    let startY = height / 2 - 120;
    let endY = height / 2 + 30;

    //组成记录文字点的信息
    textCoordinateList.current = [];
    for (let i = startX; i <= endX; i += 5) {
      for (let j = startY; j <= endY; j += 5) {
        let imageData = canvasCtx.current.getImageData(i, j, 2, 2);
        let data = imageData.data
        if (data[0] == 255 && data[1] == 0 && data[2] == 0) {
          textCoordinateList.current.push({
            x: i,
            y: j,
          })
        }
      }
    }
  }, [width, height])

  /** 设置点到达坐标*/
  const setArrivalCoordinate = useCallback(() => {
    for (let i = 0; i < allDot.current.length; i++) {
      let x = 0;
      let y = 0;
      if (map.current.has(i)) {
        x = textCoordinateList.current[map.current.get(i)].x;
        y = textCoordinateList.current[map.current.get(i)].y;
      } else {
        x = createRandomNum(0, width)
        y = createRandomNum(0, height)
      }

      allDot.current[i] = {
        ...allDot.current[i],
        toX: x,
        toY: y,
        speedX: ((x - allDot.current[i].x) / 2000 * 17),
        speedY: ((y - allDot.current[i].y) / 2000 * 17),
        isArrive: false,
      }
    }
  }, [width, height])

  /**  创建映射关系*/
  const createMap = useCallback(() => {
    map.current.clear()
    var numbers = [];
    for (var i = 0; i < allDot.current.length; i++) {
      numbers.push(i);
    }
    var randomNumbers = [];
    for (var j = 0; j < textCoordinateList.current.length; j++) {
      var randomIndex = createRandomNum(0, numbers.length - 1)
      randomNumbers.push(numbers[randomIndex]);
      map.current.set(numbers[randomIndex], j);
      numbers.splice(randomIndex, 1);
    }
  }, [])

  /** 动画*/
  const onScatter = useCallback(() => {
    setLiteralCoordinate()
    createMap()
    setArrivalCoordinate()
    drawMove()
  }, [drawMove, setLiteralCoordinate, setArrivalCoordinate])

  /** 视口大小变化*/
  const onReSize = useCallback(() => {
    if (frameDom.current === null) { 
      return
    }
    let { height, width } = frameDom.current.getBoundingClientRect();

    setHeight(height)
    setWidth(width)
  }, [])

  /** 初始化*/
  useEffect(() => {
    if (canvasDom.current === null) {
      return
    }
    canvasCtx.current = canvasDom.current.getContext('2d')

    /** 初始化*/
    let { height, width } = frameDom.current.getBoundingClientRect();
    setHeight(height)
    setWidth(width)
  }, [])

  useEffect(() => {
    requestAnimationFrame(() => {
      for (let i = 0; i < dotTotal.current; i++) {
        let x = createRandomNum(0, width)
        let y = createRandomNum(0, height)
        let color = getRandomColor()
        pointPlot(x, y, color)
        allDot.current[i] = {
          x, y, color,
          toX: 0,
          toY: 0,
          speedX: 0,
          speedY: 0,
          isArrive: false,
        }
      }
      onScatter()
    })

  }, [width, height])


  useEffect(() => {
    if (frameDom.current === null) {
      return
    }
    const resizeObserver = new ResizeObserver(entries => {
      onReSize()
    });

    resizeObserver.observe(frameDom.current);
    return () => {
      resizeObserver.disconnect()
    }
  }, [])

  return (
    <>
      <FrameStyle
        ref={frameDom}
        onClick={onScatter}
      >
        <CanvasStyle
          ref={canvasDom} width={width} height={height}>
        </CanvasStyle>
      </FrameStyle>
    </>
  )
}

export default WithErrorBoundary(ParticleText)